package service

import (
	"context"
	"crypto/rsa"
	"regexp"
	"sync"
	"time"

	"bmember/internal/dao"
	"bmember/internal/object"

	"github.com/dgrijalva/jwt-go"
	"github.com/pkg/errors"

	"bmember/pkg/lueerror"
)

var (
	privKey           *rsa.PrivateKey
	pubKey            *rsa.PublicKey
	pubOnce, privOnce sync.Once
)

type (
	// JwtPayload auth 认证相关
	JwtPayload struct {
		MemberID int64  `json:"member_id"`
		BrandID  int64  `json:"brand_id"`
		OpenID   string `json:"open_id"`
		AppID    string `json:"app_id"`
		BilinApp string `json:"bilin_app"`
	}
	loginClaims struct {
		jwt.StandardClaims
		*JwtPayload
	}
)

// ExtractToken 提取 token
func extractToken(token string) string {
	reg, _ := regexp.Compile(`^[bB]earer (([a-zA-z0-9\-_]+\.){2}[a-zA-z0-9\-_]+)$`)
	authReg := reg.FindStringSubmatch(token)
	if len(authReg) != 3 {
		return token
	}
	return authReg[1]
}

// LoadPubKey 加载公钥
func loadPubKey(key string) func() {
	return func() {
		pubKey, _ = jwt.ParseRSAPublicKeyFromPEM([]byte(key))
	}
}

// LoadPrivKey 加载私钥
func loadPrivKey(key string) func() {
	return func() {
		privKey, _ = jwt.ParseRSAPrivateKeyFromPEM([]byte(key))
	}
}

// VerifyJwtToken jwt token 校验
func verifyJwtToken(tokenStr string) (*JwtPayload, error) {
	parse := &jwt.Parser{
		ValidMethods:         nil,
		UseJSONNumber:        false,
		SkipClaimsValidation: true,
	}
	token, err := parse.ParseWithClaims(tokenStr, &loginClaims{}, func(token *jwt.Token) (i interface{}, e error) {
		return pubKey, nil
	})
	if err != nil {
		return nil, err
	}
	claims := token.Claims.(*loginClaims)
	err = claims.Valid()
	if err != nil {
		return nil, err
	}
	return claims.JwtPayload, nil
}

// GenJwtToken jwt token 生成
func genJwtToken(payload *JwtPayload, issuedAt, expiredAt int64) (string, error) {
	t := jwt.New(jwt.SigningMethodRS256)
	t.Claims = &loginClaims{
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expiredAt,
			IssuedAt:  issuedAt,
		},
		JwtPayload: payload,
	}
	return t.SignedString(privKey)
}

// GenJwtToken 生成登陆token
func (srv *Service) GenJwtToken(memberId, brandId int64, openId, appId, scene string) (string, lueerror.LueError) {
	privOnce.Do(loadPrivKey(srv.cfg.Keys.PrivKey))
	now := time.Now()
	issuedAt := now.Unix()
	expiredAt := now.Add(srv.cfg.Keys.TokenDuration).Unix()

	payload := &JwtPayload{
		MemberID: memberId,
		BrandID:  brandId,
		OpenID:   openId,
		AppID:    appId,
		BilinApp: scene,
	}
	token, err := genJwtToken(payload, issuedAt, expiredAt)
	if err != nil {
		return "", lueerror.AuthGrantErr(err)
	}
	return token, nil
}

// VerifyToken token 校验
func (srv *Service) VerifyToken(token string) (*JwtPayload, lueerror.LueError) {
	if token == "" {
		return nil, lueerror.AuthGrantErr(errors.New("无效的token"))
	}
	token = extractToken(token)
	pubOnce.Do(loadPubKey(srv.cfg.Keys.PubKey))

	payload, err := verifyJwtToken(token)
	if err != nil {
		return nil, lueerror.AuthGrantErr(err)
	}
	if payload == nil {
		return nil, lueerror.AuthGrantErr(errors.New("jwt payload 为空"))
	}
	return payload, nil
}

// AdminPermissionCheck 比邻后台权限校验
func (srv *Service) AdminPermissionCheck(ctx context.Context, token, perRouter string) (bool, *object.AdminToken, lueerror.LueError) {
	token = extractToken(token)
	hasPerm, EmpToken, err := dao.GetBilinVendorDao().PermissionCheck(ctx, token, perRouter)
	if err != nil {
		return false, nil, lueerror.HasNoPermission()
	}
	return hasPerm, EmpToken, nil
}
